////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                                                            //
//                                                         WNProject                                                          //
//                                                                                                                            //
//            This file is distributed under the BSD 2-Clause open source license. See License.txt for details.               //
//                                                                                                                            //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#pragma once

#ifndef __WN_MATH_INTERNAL_X86_ELEMENTS_H__
#define __WN_MATH_INTERNAL_X86_ELEMENTS_H__

#ifndef __WN_MATH_INTERNAL_ELEMENTS_H__
    #error "Internal/x86/WNElements.h should never be included directly."
#elif !defined _WN_X86
    #error "Internal/x86/WNElements.h has been included on a non x86 platform. Please rectify this."
#elif !defined __WN_MATH_ENABLE_EXTENSIONS
    #error "Internal/x86/WNElements.h has been included when extensions are disabled. Please rectify this."
#endif

#ifdef __WN_MATH_SSE_ALLOWED
    #include "WNMath/inc/Internal/x86/SSE/WNElements.h"
#endif

#ifdef __WN_MATH_SSE2_ALLOWED
    #include "WNMath/inc/Internal/x86/SSE2/WNElements.h"
#endif

#ifdef __WN_MATH_AVX_ALLOWED
    #include "WNMath/inc/Internal/x86/AVX/WNElements.h"
#endif

#define __WN_MATH_USE_GENERIC WN_FLOAT32 // replace with genertic functions

namespace WNMath {
    namespace __WNInternal {
        template <WN_UINT32 Dimension>
        class __WNElementsAdd<WN_FLOAT32, Dimension, typename WNCore::WNEnableWhen<(Dimension > 1)>::Value> {
        public:
            static WN_FORCE_INLINE __WNElements<WN_FLOAT32, Dimension>& Execute(__WNElements<WN_FLOAT32, Dimension>& _element1, const __WNElements<WN_FLOAT32, Dimension>& _element2) {
                #ifdef __WN_MATH_SSE_FORCED
                    return(__WNElementsAddSSE<Dimension>(_element1, _element2));
                #else
                    #ifdef __WN_MATH_SSE_ALLOWED
                        if (WNCore::WNSystem::WNHasSSE()) {
                            return(__WNElementsAddSSE<Dimension>(_element1, _element2));
                        }
                    #endif

                    return(__WNElementsAddGeneric<WN_FLOAT32, Dimension>::Execute(_element1, _element2));
                #endif
            }

            static WN_FORCE_INLINE __WNElements<WN_FLOAT32, Dimension>& Execute(__WNElements<WN_FLOAT32, Dimension>& _element, const WN_FLOAT32& _number) {
                #ifdef __WN_MATH_SSE_FORCED
                    return(__WNElementsAddSSE<Dimension>(_element, _number));
                #else
                    #ifdef __WN_MATH_SSE_ALLOWED
                        if (WNCore::WNSystem::WNHasSSE()) {
                            return(__WNElementsAddSSE<Dimension>(_element, _number));
                        }
                    #endif

                    return(__WNElementsAddGeneric<WN_FLOAT32, Dimension>::Execute(_element, _number));
                #endif
            }

        private:
            __WNElementsAdd() {}
        };

        template <WN_UINT32 Dimension>
        class __WNElementsSubtract<WN_FLOAT32, Dimension, typename WNCore::WNEnableWhen<(Dimension > 1)>::Value> {
        public:
            static WN_FORCE_INLINE __WNElements<WN_FLOAT32, Dimension>& Execute(__WNElements<WN_FLOAT32, Dimension>& _element1, const __WNElements<WN_FLOAT32, Dimension>& _element2) {
                #ifdef __WN_MATH_SSE_FORCED
                    return(__WNElementsSubtractSSE<Dimension>(_element1, _element2));
                #else
                    #ifdef __WN_MATH_SSE_ALLOWED
                        if (WNCore::WNSystem::WNHasSSE()) {
                            return(__WNElementsSubtractSSE<Dimension>(_element1, _element2));
                        }
                    #endif

                    return(__WNElementsSubtractGeneric<WN_FLOAT32, Dimension>::Execute(_element1, _element2));
                #endif
            }

            static WN_FORCE_INLINE __WNElements<WN_FLOAT32, Dimension>& Execute(__WNElements<WN_FLOAT32, Dimension>& _element, const WN_FLOAT32& _number) {
                #ifdef __WN_MATH_SSE_FORCED
                    return(__WNElementsSubtractSSE<Dimension>(_element, _number));
                #else
                    #ifdef __WN_MATH_SSE_ALLOWED
                        if (WNCore::WNSystem::WNHasSSE()) {
                            return(__WNElementsSubtractSSE<Dimension>(_element, _number));
                        }
                    #endif

                    return(__WNElementsSubtractGeneric<WN_FLOAT32, Dimension>::Execute(_element, _number));
                #endif
            }

        private:
            __WNElementsSubtract() {}
        };

        template <WN_UINT32 Dimension>
        class __WNElementsMultiply<WN_FLOAT32, Dimension, typename WNCore::WNEnableWhen<(Dimension > 1)>::Value> {
        public:
            static WN_FORCE_INLINE __WNElements<WN_FLOAT32, Dimension>& Execute(__WNElements<WN_FLOAT32, Dimension>& _element1, const __WNElements<WN_FLOAT32, Dimension>& _element2) {
                #ifdef __WN_MATH_SSE_FORCED
                    return(__WNElementsMultiplySSE<Dimension>(_element1, _element2));
                #else
                    #ifdef __WN_MATH_SSE_ALLOWED
                        if (WNCore::WNSystem::WNHasSSE()) {
                            return(__WNElementsMultiplySSE<Dimension>(_element1, _element2));
                        }
                    #endif

                        return(__WNElementsMultiplyGeneric<WN_FLOAT32, Dimension>::Execute(_element1, _element2));
                #endif
            }

            static WN_FORCE_INLINE __WNElements<WN_FLOAT32, Dimension>& Execute(__WNElements<WN_FLOAT32, Dimension>& _element, const WN_FLOAT32& _number) {
                #ifdef __WN_MATH_SSE_FORCED
                    return(__WNElementsMultiplySSE<Dimension>(_element, _number));
                #else
                    #ifdef __WN_MATH_SSE_ALLOWED
                        if (WNCore::WNSystem::WNHasSSE()) {
                            return(__WNElementsMultiplySSE<Dimension>(_element, _number));
                        }
                    #endif

                    return(__WNElementsMultiplyGeneric<WN_FLOAT32, Dimension>::Execute(_element, _number));
                #endif
            }

        private:
            __WNElementsMultiply() {}
        };

        template <WN_UINT32 Dimension>
        class __WNElementsDivide<WN_FLOAT32, Dimension, typename WNCore::WNEnableWhen<(Dimension > 1)>::Value> {
        public:
            static WN_FORCE_INLINE __WNElements<WN_FLOAT32, Dimension>& Execute(__WNElements<WN_FLOAT32, Dimension>& _element1, const __WNElements<WN_FLOAT32, Dimension>& _element2) {
                #ifdef __WN_MATH_SSE_FORCED
                    return(__WNElementsDivideSSE<Dimension>(_element1, _element2));
                #else
                    #ifdef __WN_MATH_SSE_ALLOWED
                        if (WNCore::WNSystem::WNHasSSE()) {
                            return(__WNElementsDivideSSE<Dimension>(_element1, _element2));
                        }
                    #endif

                    return(__WNElementsDivideGeneric<WN_FLOAT32, Dimension>::Execute(_element1, _element2));
                #endif
            }

            static WN_FORCE_INLINE __WNElements<WN_FLOAT32, Dimension>& Execute(__WNElements<WN_FLOAT32, Dimension>& _element, const WN_FLOAT32& _number) {
                #ifdef __WN_MATH_SSE_FORCED
                    return(__WNElementsDivideSSE<Dimension>(_element, _number));
                #else
                    #ifdef __WN_MATH_SSE_ALLOWED
                        if (WNCore::WNSystem::WNHasSSE()) {
                            return(__WNElementsDivideSSE<Dimension>(_element, _number));
                        }
                    #endif

                    return(__WNElementsDivideGeneric<WN_FLOAT32, Dimension>::Execute(_element, _number));
                #endif
            }

        private:
            __WNElementsDivide() {}
        };

        template <WN_UINT32 Dimension>
        class __WNElementsMultiplyAdd<WN_FLOAT32, Dimension, typename WNCore::WNEnableWhen<(Dimension > 1)>::Value> {
        public:
            static WN_FORCE_INLINE __WNElements<WN_FLOAT32, Dimension>& Execute(__WNElements<WN_FLOAT32, Dimension>& _element1, const __WNElements<WN_FLOAT32, Dimension>& _element2, const __WNElements<WN_FLOAT32, Dimension>& _element3) {
                #ifdef __WN_MATH_SSE_FORCED
                    return(__WNElementsMultiplyAddSSE<Dimension>(_element1, _element2, _element3));
                #else
                    #ifdef __WN_MATH_SSE_ALLOWED
                        if (WNCore::WNSystem::WNHasSSE()) {
                            return(__WNElementsMultiplyAddSSE<Dimension>(_element1, _element2, _element3));
                        }
                    #endif

                    return(__WNElementsMultiplyAddGeneric<WN_FLOAT32, Dimension>::Execute(_element1, _element2, _element3));
                #endif
            }

            static WN_FORCE_INLINE __WNElements<WN_FLOAT32, Dimension>& Execute(__WNElements<WN_FLOAT32, Dimension>& _element, const WN_FLOAT32 _number1, const WN_FLOAT32 _number2) {
                #ifdef __WN_MATH_SSE_FORCED
                    return(__WNElementsMultiplyAddSSE<Dimension>(_element, _number1, _number2));
                #else
                    #ifdef __WN_MATH_SSE_ALLOWED
                        if (WNCore::WNSystem::WNHasSSE()) {
                            return(__WNElementsMultiplyAddSSE<Dimension>(_element, _number1, _number2));
                        }
                    #endif

                    return(__WNElementsMultiplyAddGeneric<WN_FLOAT32, Dimension>::Execute(_element, _number1, _number2));
                #endif
            }

        private:
            __WNElementsMultiplyAdd() {}
        };

        template <WN_UINT32 Dimension>
        class __WNElementsMultiplySubtract<WN_FLOAT32, Dimension, typename WNCore::WNEnableWhen<(Dimension > 1)>::Value> {
        public:
            static WN_FORCE_INLINE __WNElements<WN_FLOAT32, Dimension>& Execute(__WNElements<WN_FLOAT32, Dimension>& _element1, const __WNElements<WN_FLOAT32, Dimension>& _element2, const __WNElements<WN_FLOAT32, Dimension>& _element3) {
                #ifdef __WN_MATH_SSE_FORCED
                    return(__WNElementsMultiplySubtractSSE<Dimension>(_element1, _element2, _element3));
                #else
                    #ifdef __WN_MATH_SSE_ALLOWED
                        if (WNCore::WNSystem::WNHasSSE()) {
                            return(__WNElementsMultiplySubtractSSE<Dimension>(_element1, _element2, _element3));
                        }
                    #endif

                    return(__WNElementsMultiplySubtractGeneric<WN_FLOAT32, Dimension>::Execute(_element1, _element2, _element3));
                #endif
            }

            static WN_FORCE_INLINE __WNElements<WN_FLOAT32, Dimension>& Execute(__WNElements<WN_FLOAT32, Dimension>& _element, const WN_FLOAT32 _number1, const WN_FLOAT32 _number2) {
                #ifdef __WN_MATH_SSE_FORCED
                    return(__WNElementsMultiplySubtractSSE<Dimension>(_element, _number1, _number2));
                #else
                    #ifdef __WN_MATH_SSE_ALLOWED
                        if (WNCore::WNSystem::WNHasSSE()) {
                            return(__WNElementsMultiplySubtractSSE<Dimension>(_element, _number1, _number2));
                        }
                    #endif

                    return(__WNElementsMultiplySubtractGeneric<WN_FLOAT32, Dimension>::Execute(_element, _number1, _number2));
                #endif
            }

        private:
            __WNElementsMultiplySubtract() {}
        };

        template <WN_UINT32 Dimension>
        class __WNElementsSet<WN_FLOAT32, Dimension, typename WNCore::WNEnableWhen<(Dimension > 1)>::Value> {
        public:
            static WN_FORCE_INLINE __WNElements<WN_FLOAT32, Dimension>& Execute(__WNElements<WN_FLOAT32, Dimension>& _element, const WN_FLOAT32& _number) {
                #ifdef __WN_MATH_SSE_FORCED
                    return(__WNElementsSetSSE<Dimension>(_element, _number));
                #else
                    return(__WNElementsSetGeneric<WN_FLOAT32, Dimension>::Execute(_element, _number));
                #endif
            }

            static WN_FORCE_INLINE __WNElements<WN_FLOAT32, Dimension>& Execute(__WNElements<WN_FLOAT32, Dimension>& _element, const WN_FLOAT32* _numbers) {
                #ifdef __WN_MATH_SSE_FORCED
                    return(__WNElementsSetSSE<Dimension>(_element, _numbers));
                #else
                    return(__WNElementsSetGeneric<WN_FLOAT32, Dimension>::Execute(_element, _numbers));
                #endif
            }

        private:
            __WNElementsSet() {}
        };
    }
}

#endif // __WN_MATH_INTERNAL_X86_ELEMENTS_H__